package com.notetool.util.config;

import java.util.ArrayList;
import java.util.HashMap;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.notetool.util.StringHelper;

/* 
 * 
 * Eventuall, if we want to further this...
 * 
 * We could allow @ as a modifier, like ~.
 * The @ modifier would mean we're looking for an attribute.
 * For example, "/test/@attrib" would mean we're looking for
 * all "test" elements and then all the first level children with "attrib".
 * We could probably combine it with ~...
 * For example, "/test/@~attrib" would mean we're looking for all
 * "test" elements and then all the children elements with "attrib"
 * as an attribute.
 * Then we could have a getAttribute(attribute) method for getting attribute values on a set.
 * Let's say we wanted to find all the "GameElement" attributes in a file...
 * 
 * ConfigSet[] gameElementsSets;
 * ConfigSet set = AppConfig.getConfig ();
 * while (set.empty())
 * {
 *     set = config.getSet("~@GameElement");
 *     gameElementsSets.add (set);
 * }
 * 
 * We could potentially try combining elements and attributes in the same lookup...
 * "/test/test2@attr1@attr2/"
 * 
 * Would also be nice to be able to explore.
 * Like ConfigSet set = AppConfig.getConfig();
 * set.getElements(); -> nom d'elements... c pas mal tout ce qui faut pour explorer
 * set.getAttributes();
 * 
 */

public class ConfigSet
{
    ////////////////////
    // Members
    private ArrayList<Node> elements;
    
    ////////////////////
    // Constructors
    public ConfigSet (Node element)
    {
        elements = new ArrayList<Node> ();
        
        if (element == null)
            return;
        
        if (!(element instanceof Document || element instanceof Element))
            throw new InvalidConfigSetNode ();
        
            elements.add(element);
    }
    
    private ConfigSet (ArrayList<Node> elementList)
    {
        elements = elementList;
    }
    
    ////////////////////
    // Private static methods
    private static ArrayList<Node> getFirstLevelChildren (ArrayList<Node> elements, String childrenName)
    {
        ArrayList<Node> children = new ArrayList<Node> ();
        
        // Loop through elements
        for (Node element : elements)
        {
            // Loop through children elements
            NodeList list = NodeHelper.getElementsByTagName(element, childrenName);
            for (int i = 0; i < list.getLength (); i ++)
            {
                Node child = list.item(i);
                
                // Only keep if first level child
                if (child.getParentNode() == element)
                    children.add(child);
            }
        }
        
        return children;
    }
    
    private static ArrayList<Node> getChildren (ArrayList<Node> elements, String childrenName)
    {
        ArrayList<Node> children = new ArrayList<Node> ();
        
        // Loop through elements
        for (Node element : elements)
        {
            // Loop through children elements
            NodeList list = NodeHelper.getElementsByTagName(element, childrenName);
            for (int i = 0; i < list.getLength (); i ++)
            {
                Node child = list.item(i);
                
                // Only keep if there isn't one that's higher in the hierarchy below element with the same name...
                if (!sameNameParentExists(child, element))
                    children.add(child);
            }
        }
        
        return children;
    }
    
    private static boolean sameNameParentExists (Node element, Node root)
    {
        if (element == null)
            return false;
        
        // Loop over the parents till we reach the top-level
        Node parent = element;
        while ((parent = parent.getParentNode ()) != null)
        {
            // If we've reached the root of the search, abandon
            if (parent == root)
                return false;
            
            // If we find a parent with the same name, we're done
            if (element.getNodeName ().equals(parent.getNodeName ()))
                return true;
        }
        
        return false;
    }
    
    private static boolean isLeaf (Node element)
    {
        return (NodeHelper.getElementsByTagName (element, "*").getLength () == 0);
    }
    
    private static ArrayList<Node> getParents (ArrayList<Node> elements)
    {
        ArrayList<Node> parents = new ArrayList<Node> ();
        
        // If no list to work with, return empty parents list
        if (elements == null || elements.isEmpty ())
            return parents;
        
        // Start getting the parents
        for (Node element : elements)
        {
            // Shouldn't be null, but in case..
            if (element == null)
                continue;
            
            // Get parent and add if not null/not already there
            Node parent = element.getParentNode ();
            
            if (parent != null && !parents.contains(parent))
                parents.add(parent);
        }
        
        return parents;
    }
    
    ////////////////////
    // Public methods
    public ConfigSet getSet (String path)
    {
        // get the path
        ConfigPath confPath = ConfigPath.fromString(path);
        
        // Start recursive call
        return getSetRecursive (confPath);
    }
    
    public String getFirstValue ()
    {
        ArrayList<String> values = getValues ();
        
        if (values.isEmpty ())
            return null;
        
        return values.get (0);
    }
    
    public ArrayList<String> getValues ()
    {
        ArrayList<String> values = new ArrayList<String> ();
        
        for (Node element : elements)
        {
            if (!isLeaf (element))
                continue;
            
            values.add (element.getTextContent ().trim ());
        }
        
        return values;
    }
    
    ////////////////////
    // Private methods
    private ConfigSet getSetRecursive (ConfigPath confPath)
    {
        // If no token left, return self
        if (confPath.isEmpty ())
            return this;
        
        // Grab token
        String token = confPath.pullToken ();
        
        // If token is ".", return self
        if (token.equals("."))
            return getSetRecursive(confPath);
        
        // If token is "..", then get parents
        if (token.equals(".."))
            return new ConfigSet (getParents (elements)).getSetRecursive (confPath);
        
        // Check if we're doing a non first-level search
        if (token.startsWith("~"))
            return new ConfigSet (getChildren (elements, token.substring (1))).getSetRecursive (confPath);
        
        // Get first level children
        return new ConfigSet (getFirstLevelChildren (elements, token)).getSetRecursive (confPath);
    }
    
}
